#include "xthread_manager.h"
#include "xrt_error.h"
#include "xassert.h"
#include "xsys_log.h"
#include "xatexit_manager.h"

#include <process.h>
#include <new.h>

#ifdef _DEBUG
#define CHECK_INITIALIZE XASSERT(IsInitializedBefore())
#else
#define CHECK_INITIALIZE
#endif

namespace xge{
namespace xge_internal{

  void ThreadManager::SleepCurrentThread( size_t ms ) {
    ::Sleep(ms);
  }

  void ThreadManager::SwithToAnotherThread() {
    ::SwitchToThread();
  }

  size_t ThreadManager::GetCurrentThreadUniqueID() {
    return (size_t)::GetCurrentThreadId();
  }

  ThreadHandlerPtr 
    ThreadManager::CreateXThread( 
    int* error_code , 
    const ThreadDelegatePtr& thread_delegate , 
    const String& thread_name , 
    const char* debug_name /* = NULL  */, 
    bool right_away /* = true */ ) {
      CHECK_INITIALIZE;
      
    // We first insert a NULL tag into the thread_handler_map ( Thread Safe Insert )
    // since after _beginthreadex calling , we do not know when the thread will be running
    // so pre-inserting is safe 
    // if the create thread encounter error , we just erase this insertion .

    XContext::InsertRet ret = kContext->Insert(
      thread_name,
      (XThreadManageData*)(NULL)
      );

    // Name collision 
    // a same name thread has been created before without erasing
    if( ret.second == false ) {
      if(error_code)
        *error_code = EC_NAME_COLLISION;
      thread_delegate->status_ = ThreadDelegate::THREAD_STATUS_RELEASE;
      return ThreadHandlerPtr();
    }
    // allocate the run time data here
    XThreadManageData* data = new XThreadManageData(
      new ThreadHandler(thread_name,INVALID_HANDLE_VALUE),
      thread_delegate
      );
    ret.first->second = data;
    // create the thread using _beginthreadex
    // Why we use this instead of CreateThread
    // because we will take use of CRT , if we use CreateThread 
    // we will have a little problem of memory leak 
    // the pid_t will be allocated without free
    // _beginthreadex is almost the same as the CreateThread .
    thread_delegate->status_ = ThreadDelegate::THREAD_STATUS_RUNNING;
    ::HANDLE handle = (::HANDLE)(_beginthreadex( 
      NULL, // null privilege descriptor
      0 ,   // dynamically increased stack size
      ThreadManager::ThreadMainFunc,
      data,
      right_away ? 0 : CREATE_SUSPENDED, // created option
      NULL
      ));
    if( handle == INVALID_HANDLE_VALUE ) {
      // Although we do not use CreateThread 
      // however , the _beginthreadex internally 
      // call the CreateThread
      // so getting the GetLastError will be helpful
      WIN32_API_WARNING("CreateThread");
      // Say a hint here 
      SYS_LOG(PANIC,true,_xT("_beginthreadex has failed,thread creation error! \
                              See more error in CreateThread error information!"));
      if(error_code) 
        *error_code = EC_CREATE_ERROR;
      delete data;
      thread_delegate->status_ = ThreadDelegate::THREAD_STATUS_RELEASE;
      // do the erasing here
      kContext->Erase(ret.first);
      return ThreadHandlerPtr(); // return an error stuff
    }
    // Validate the handler here
    (*(data->thread_handler)).thread_handler_ = handle;
    // set the debug name 
    if(debug_name)
      (data->thread_handler)->SetDebugName(debug_name);
    // ok  here 
    if(error_code)
      *error_code = EC_OK;
    return (data->thread_handler);
  }

  unsigned int __stdcall ThreadManager::ThreadMainFunc( void* argument ) {
    XThreadManageData* data = static_cast<XThreadManageData*>(argument);
    XASSERT( (data->thread_delegate->status_) == ThreadDelegate::THREAD_STATUS_RUNNING );
    (data->thread_delegate)->status_ = ThreadDelegate::THREAD_STATUS_RELEASE;
    (data->thread_delegate)->ThreadMain();
    ThreadManager::NotifyThreadTermination(
      (data->thread_handler)->name()
      );
    return XTHREAD_GENERAL_EXIT_CODE;
  }

  bool ThreadManager::NotifyThreadTermination( const String& thread_name ) {
    CHECK_INITIALIZE;
    XContext::Iterator ret = kContext->Find(thread_name);
    if( kContext->End(ret) ) {
      return false;
    }
    (((*ret).second)->thread_delegate)->status_ = ThreadDelegate::THREAD_STATUS_RELEASE;
    kContext->Erase(ret);
    return true;
  }

  // Not indicate it is null is ok 
  // C++ standard says that , any global ( stack ) static varible will set to 0
  ThreadManager::XContext* ThreadManager::kContext = NULL;
  size_t ThreadManager::kMainThreadID ;

  bool ThreadManager::DestroyXThreadManager( void* argument ) {
    CHECK_INITIALIZE;

    DUMMY_ARG(argument);
    XContext::RunningThreadHandlerMap& m = kContext->GetMap();
    for( XContext::Iterator ib = m.begin() , ie = m.end() ; ib != ie ; ++ib ) {
      ThreadHandlerPtr& handle = ((*ib).second)->thread_handler;
      if( handle->IsRunning() ) {
        // If a thread is still running here
        // we have no way to stop its execution here
        // so we just wait for this thread until it is
        // really exit 
        handle->Join( kAtExitMaxJoinTime );
        // If it is still running , ok , we have no idea but passing
        // an panic into the file and leave it
        if( handle->IsRunning() )
          SYS_LOG(PANIC,true,_xT("Detecting a thread: %s is still running when the ThreadManager destroying in "),
                                _xT("XAtExitManager callback , so there must be some resource error here! "),
                    handle->name().c_str());
        // Since this thread is still running 
        // to avoid the potential memory bug
        // we do not delete this handle just leave this
        // handle leaky .Let the OS take the next work from here.
        continue;
      }
      delete (*ib).second;
    }
    m.clear();
    return true;
  }

  bool ThreadManager::Initialize() {
    // Initialize the ThreadManager
#ifdef _DEBUG
    XASSERT(!IsInitializedBefore());
#endif
    kContext = new XContext();
    kMainThreadID = GetCurrentThreadUniqueID();
    // Register the destroyer
    AtExitManager::RegisterProxy(
      DestroyXThreadManager,
      NULL, 
      XTHREADMANAGER_DESTROY_PRIORITY,
      _xT("ThreadManager at-exit-callback error!")
      );
    return true;
  }


#undef CHECK_INITIALIZE


} // namespace xge_internal
}// namespace xge